//@ts-nocheck
import {CLASS_COMPONENT, ELEMENT, FUNCTION_COMPONENT, TEXT} from './constants';
import {onlyOne, setProps, flatten, patchProps} from './util';

export function createDOM(element){
  element = onlyOne(element); // emmm todo
  const {$$typeof} = element;
  let dom = null;
  if(!$$typeof){ // element 是一个字符串或则数字 ReactDOM.render(123,document.getElementById('root'))
    dom = document.createTextNode(element);
  }else if($$typeof === TEXT){
    dom = document.createTextNode(element.content);
  }else if($$typeof === ELEMENT){
    dom = createNativeDOM(element);
  }else if($$typeof === FUNCTION_COMPONENT){
    dom = createFunctionComponentDOM(element);
  }else if($$typeof === CLASS_COMPONENT){
    dom = createClassComponentDOM(element);
  }

  // 不管什么类型元素 都让它的dom属性指向他创建出来的真实dom元素 compareTwoElements方法中会用到
  element.dom = dom;

  return dom;
}

/** 创建函数组件对应的真实DOM对象*/
function createFunctionComponentDOM(element){
  const {type, props} = element;
  const renderElement = type(props);
  element.renderElement = renderElement; // 指向上一次渲染的虚拟DOM节点 用于dom diff
  return createDOM(renderElement);
}

/** 创建类组件对应的真实DOM对象*/
function createClassComponentDOM(element){
  const {type, props} = element;
  const componentInstance = new type(props);
  element.componentInstance = componentInstance;
  const renderElement = componentInstance.render();
  componentInstance.renderElement = renderElement; // 指向上一次渲染的虚拟DOM节点 用于dom diff 参看component.tsx中的forceUpdate方法
  return createDOM(renderElement);
}


function createNativeDOM(element){
  const {type, props} = element;
  const dom = document.createElement(type); // 真实的BUTTON DOM对象
  // 1. 创建此虚拟DOM节点的子节点
  createDOMChildren(dom, element.props.children);
  setProps(dom, props);
  // 2. 给此DOM元素添加属性
  return dom;
}

function createDOMChildren(parentNode, children){
  children && /*children.flat(Infinity)*//**←这个是深克隆的*/flatten(children).forEach((child, index) => {

    //child是虚拟dom,我们会在虚拟DOM上加一个属性_mountIndex,指向此DOM节点在父节点中的索引
    //在后面我们做dom-diff的时候有用
    child._mountINdex = index;

    const childDOM = createDOM(child); // 创建此虚拟DOM节点的真实DOM元素
    parentNode.appendChild(childDOM);
  });
}

/** ↓由类组件component forceUpdate调用*/
export function compareTwoElements(oldRenderElement, newRenderElement) {
  oldRenderElement = onlyOne(oldRenderElement);
  newRenderElement = onlyOne(newRenderElement);

  const currentDOM = oldRenderElement/** ←可能是null,因为可能是条件组件*/ && oldRenderElement.dom;

  let currentElement = oldRenderElement;

  //1. 新的没了, 直接移除老节点
  if (newRenderElement == null) { // return ({this.state.number==0?<div></div>:null})
    currentDOM.parentNode.removeChild(currentDOM);
    currentElement = null;

  //2. 节点类型不一致 直接用新的替换旧的
  } else if (oldRenderElement.type != newRenderElement.type) { ///span(字符串) div(字符串)... function(函数本身) class(类本身)   文本节点的话 比较特殊 它的type = TEXT = Symbol.for('TEXT')
    const newDom = createDOM(newRenderElement);
    currentDOM.parentNode.replaceChild(newDom, currentDOM);
    currentElement = newRenderElement;
    currentElement.dom = newDom;

  //3. 节点类型一致, 进行深度dom diff
  } else {
    // 新老节点都有,并且类型一样。 div span 都要进行dom diff 深度比较 比较他们的属性和他们的子节点
    updateElement(oldRenderElement, newRenderElement)
    // 不会改变类组件实例上的renderElement指向
    // 1. 如果是函数组件,会通过renderElement找到真实dom,更新其自身的属性和深层diff
    // 2. 如果是类组件,是用新的属性来调用自身updater上的emitUpdate让自身属性更新 并让自身对应的真实dom更新
  }

  console.log('return currentElement',currentElement);
  return currentElement;
}

/**
 * 进行dom diff 深度比较 比较他们的属性和他们的子节点
 *
 * 进入到此函数说明它们的 .type是一样的
 * */
function updateElement(oldElement, newElement){
  const currentDOM = newElement.dom = oldElement.dom;

  // 是文本节点 如果老的文本内容 和 新的文本内容不一样就更新
  if(oldElement.$$typeof === TEXT && newElement.$$typeof === TEXT){
    if(oldElement.content !== newElement.content) currentDOM.textContent = newElement.content;

  // 如果是元素类型节点 div、span这些
  }else if (oldElement.$$typeof === ELEMENT) {
    updateDOMProperties(currentDOM, oldElement.props, newElement.props);
    updateChildrenElements(currentDOM, oldElement.props.children, newElement.props.children);

  // 参看test-case/03/01updateFunctionComponent.tsx
  // return ( <FunctionCounter number={this.state.number} handleClick={this.handleClick}/> )
  } else if (oldElement.$$typeof === FUNCTION_COMPONENT) {
    updateFunctionComponent(oldElement, newElement);

  // 参看test-case/03/02updateClassComponent.tsx
  } else if (oldElement.$$typeof === CLASS_COMPONENT) {
    updateClassComponent(oldElement, newElement);
  }
}

function updateChildrenElements(dom, oldChildrenElements, newChildrenElements){
  diff(dom, oldChildrenElements, newChildrenElements);
}

function diff(parentNode, oldChildrenElements, newChildrenElements){
  //
}

function updateFunctionComponent(oldElement, newElement){
  const oldRenderElement = oldElement.renderElement;
  const newRenderElement = newElement.type(newElement.props);
  const currentElement = compareTwoElements(oldRenderElement, newRenderElement);
  newElement.renderElement = currentElement; // 方便下一次时进行对比
}

function updateClassComponent(oldElement, newElement){
  const componentInstance = oldElement.componentInstance;
  const updater = componentInstance.$updater;
  const nextProps = newElement.props;
  updater.emitUpdate(nextProps);
}

function updateDOMProperties(dom, oldProps, newProps){
  patchProps(dom, oldProps, newProps)
}

export function ReactElement($$typeof, type, key, ref, props) {
  const element = {
    $$typeof, type, key, ref, props
  };
  return element;
}
